use std::fs::metadata;
use std::ops::Not;
use std::{sync::atomic::AtomicU64};
use std::path::Path;

use parking_lot::Mutex;

use crate::constants::{VERSION_NUM_KEY, VERSION_DEFAULT_KEY, HALF_CONTAINER_INFO_KEY, VERSION_ALL_KEY};
use crate::container::{HalfContainerInfo, ChunkContainerWriter};
use super::error::*;

#[derive(Clone,Debug)]
pub enum VersionFrom {
    Num(u64),
    Name(String),
}

pub struct VersionControlBlock {
    version_append:Mutex<Vec<VersionFrom>>,
    version_default:Mutex<Option<String>>,
    version_num:AtomicU64,
    db:sled::Db,
}

impl VersionControlBlock {
	pub fn from_path<P: AsRef<Path>+?Sized>(path:&P) -> VersionResult<Self> {
		let path:&Path = path.as_ref();
        let db = sled::open(path).map_err(vcb_open_error_from)?;
        let bytes = db.update_and_fetch(VERSION_NUM_KEY, |old|{
            match old {
                Some(old) => {
                    Some(old.to_vec())
                }
                None => {
                    Some(0u64.to_be_bytes().to_vec())
                }
            }
        }).map_err(vcb_open_error_from)?.unwrap();
        let version_num = u64::from_be_bytes((&bytes as &[u8]).try_into().unwrap());
		Ok(Self {
            version_append:Mutex::new(Vec::new()),
            version_default:Mutex::new(None),
            version_num:AtomicU64::from(version_num),
			db:db,
        })
    }

    
	pub fn open<P: AsRef<Path>+?Sized>(path:&P) -> VersionResult<Self> {
		let path:&Path = path.as_ref();
        metadata(path)?;
        let db = sled::open(path).map_err(vcb_open_error_from)?;
        let bytes = db.update_and_fetch(VERSION_NUM_KEY, |old|{
            match old {
                Some(old) => {
                    Some(old.to_vec())
                }
                None => {
                    Some(0u64.to_be_bytes().to_vec())
                }
            }
        }).map_err(vcb_open_error_from)?.unwrap();
        let version_num = u64::from_be_bytes((&bytes as &[u8]).try_into().unwrap());
		Ok(Self {
            version_append:Mutex::new(Vec::new()),
            version_default:Mutex::new(None),
            version_num:AtomicU64::from(version_num),
			db:db,
        })
    }

    pub fn new_version(&self,version_name:Option<String>) -> VersionResult<String> {
        let (version_from,version_name) = if let Some(version_name) = &version_name {
            (VersionFrom::Name(version_name.clone()),String::from(format!("version-name-{}",version_name)))
        } else {
            let version_num = self.version_num.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
            (VersionFrom::Num(version_num),String::from(format!("version-num-{}",version_num)))
        };
        self.version_append.lock().push(version_from);
        let mut version_default = self.version_default.lock();
        *version_default = Some(version_name.clone());
        self.db.insert(&version_name, vec![0])
            .map_err(db_error_from)?
            .map_or(Ok(String::from(format!("{}-index",version_name))), 
                        |_|Err(VersionError::DuplicateVersion(version_name)))
    }

    pub fn seek_version(&self,version:VersionFrom) -> VersionResult<Option<String>> {
        let version_name = match version {
            VersionFrom::Num(num) => {
                String::from(format!("version-num-{}",num))
            },
            VersionFrom::Name(name) => {
                String::from(format!("version-name-{}",name))
            }
        };
        Ok(
            self.db.contains_key(version_name.clone())
                .map_err(db_error_from)?
                .then_some(format!("{}-index",version_name))
        )
    }

    pub fn default_version(&self) -> VersionResult<Option<String>> {
        if let Some(version_default) = &*self.version_default.lock() {
            return Ok(Some(version_default.clone()));
        }
        self.db.get(VERSION_DEFAULT_KEY)
            .map(|value|
                value.map(|ivec|format!("{}-index",String::from_utf8_lossy(&ivec))))
            .map_err(db_error_from)
    }

    pub fn get_half_container_info(&self) -> VersionResult<Option<HalfContainerInfo>> {
        self.db.get(HALF_CONTAINER_INFO_KEY)
            .map(|value|
                value.map(|ivec|{
                    let mut half_container_info = HalfContainerInfo::new(0,0);
                    half_container_info.as_raw_mut().copy_from_slice(&ivec);
                    half_container_info
                }))
            .map_err(db_error_from)
    }

    pub fn keep_half_container_info(&self,chunk:&ChunkContainerWriter) -> VersionResult<()> {
        let info = chunk.half_info();
        self.db
            .fetch_and_update(HALF_CONTAINER_INFO_KEY, |_|{
                Some(info.as_raw_data())
            })
            .map(|_|())
            .map_err(db_error_from)
    }

    pub fn get_all_version(&self) -> VersionResult<Option<String>> {
        let version_append:Vec<_> = self.version_append.lock().iter().map(|v|format!("{:?}",v)).collect();
        let string_append =  version_append.join("\n");
        let string_append_clone = string_append.clone();
        self.db.get(VERSION_ALL_KEY)
            .map(|value|value.map_or(
                version_append.is_empty().not().then_some(string_append),
                |ivec|Some(format!("{}\n{}",String::from_utf8_lossy(&ivec).to_string(),string_append_clone))
            ))
            .map_err(db_error_from)
    }


    fn update_all_version(&self) -> VersionResult<()>{
        let version_append = self.version_append.lock();
        if version_append.is_empty() { return Ok(()); }
        let version_append:Vec<_> = version_append.iter().map(|v|format!("{:?}",v)).collect();
        let string_append = version_append.join("\n");
        self.db
            .fetch_and_update(VERSION_ALL_KEY, |old|{
                old.map_or(Some(string_append.as_bytes().to_vec()), |old_versions|{
                    let old_string = String::from_utf8(old_versions.to_vec()).expect("old string is not available");
                    Some(format!("{}\n{}",old_string,string_append).as_bytes().to_vec())
                })
            })
            .map(|_|())
            .map_err(db_error_from)
    }

    pub fn flush_all(&self) -> VersionResult<()> {
        self.update_all_version()?;
        self.db.fetch_and_update(VERSION_NUM_KEY, |_|{
            Some(self.version_num.load(std::sync::atomic::Ordering::Relaxed).to_be_bytes().to_vec())
        }).map_err(db_error_from)?;
        self.version_default.lock().as_ref().map_or(Ok(()), |version_default|
            self.db.fetch_and_update(VERSION_DEFAULT_KEY, |_|{
                Some(version_default.as_bytes())
            })
            .map(|_|())
            .map_err(db_error_from)
        )
    }
}

fn modify_backup(version:VersionFrom)  -> Result<(),Box<dyn std::error::Error>> {
    let vcb = VersionControlBlock::from_path("version-test")?;
    let index_name = match version {
        VersionFrom::Name(name) => {
            vcb.new_version(Some(name))?
        }
        VersionFrom::Num(_) => {
            vcb.new_version(None)?
        }
    };
    println!("backup {}",index_name);
    Ok(())
}

fn modify_restore_default()  -> Result<(),Box<dyn std::error::Error>> {
    let vcb = VersionControlBlock::from_path("version-test")?;
    println!("restore {}",vcb.default_version()?.unwrap());
    Ok(())
}

fn modify_restore_version(version:VersionFrom)  -> Result<(),Box<dyn std::error::Error>> {
    let vcb = VersionControlBlock::from_path("version-test")?;
    println!("restore {}",vcb.seek_version(version)?.unwrap());
    Ok(())
}

pub fn version_control_test() -> Result<(),Box<dyn std::error::Error>> {
    modify_backup(VersionFrom::Num(0))?;
    modify_restore_default()?;
    modify_backup(VersionFrom::Num(1))?;
    modify_restore_default()?;
    modify_backup(VersionFrom::Name("0.0.1".to_string()))?;
    modify_restore_version(VersionFrom::Name("0.0.1".to_string()))?;
    modify_restore_version(VersionFrom::Num(0))?;
    modify_restore_version(VersionFrom::Num(1))?;
    Ok(())
}
